﻿using System;
using Antlr.Runtime;
using Microsoft.VisualStudio.Package;
using Microsoft.VisualStudio.TextManager.Interop;

namespace FixtureHighlighter
{
    public class FixtureScanner : IScanner
    {
        public FixtureScanner(FixtureLanguageService languageService, IVsTextLines buffer)
        {
            LanguageService = languageService;
            Buffer = buffer;
        }


        FixtureLanguageService LanguageService { get; set; }
        IVsTextLines Buffer { get; set; }
        string Source { get; set; }
        int Offset { get; set; }

        FixtureVSLexer _lexer;
        ScannerState _state;

        internal struct ScannerState
        {
            int _state;

            public ScannerState(int state)
            {
                _state = state;
            }

            public int State
            {
                get
                {
                    return _state;
                }
                set
                {
                    _state = value;
                }
            }

            [Flags]
            enum Flags
            {
                InBlockComment = 0x1 << 0,
                InBlockString = 0x1 << 1
            }

            bool GetValue(Flags flag)
            {
                return (State & (int)flag) == (int)flag;
            }
            void SetValue(Flags flag, bool value)
            {
                if (value)
                    State |= (int)flag;
                else
                    State &= ~(int)flag;
            }

            public bool InBlockComment
            {
                get
                {
                    return GetValue(Flags.InBlockComment);
                }
                set
                {
                    SetValue(Flags.InBlockComment, value);
                }
            }

            public bool InBlockString
            {
                get
                {
                    return GetValue(Flags.InBlockString);
                }
                set
                {
                    SetValue(Flags.InBlockString, value);
                }
            }
        }

        public void SetSource(string source, int offset)
        {
            if (offset != Offset || source != Source)
            {
                Source = source;
                Offset = offset;
                _lexer = null;
            }
        }

        public bool ScanTokenAndProvideInfoAboutIt(TokenInfo tokenInfo, ref int state)
        {
            if (Offset >= Source.Length)
                return false;


            _state.State = state;
            string token_text = string.Empty;

            if (_lexer == null)
            {
                string text = Source.Substring(Offset);
                ANTLRStringStream input = new ANTLRStringStream(text);
                _lexer = new FixtureVSLexer(input);
                _lexer.InBlockComment = _state.InBlockComment;
                _lexer.InBlockString = _state.InBlockString;
            }

            if (_lexer != null)
            {
                IToken token = _lexer.NextToken();
                token_text = token.Text;
                tokenInfo.Token = token.Type;

                switch (token.Type)
                {
                    case FixtureVSLexer.IDENT:
                        tokenInfo.Type = TokenType.Identifier;
                        tokenInfo.Color = TokenColor.Keyword;
                        tokenInfo.Trigger = TokenTriggers.None;
                        break;

                    case FixtureVSLexer.REFERENCE:
                        tokenInfo.Type = TokenType.Identifier;
                        tokenInfo.Color = TokenColor.Keyword;
                        tokenInfo.Trigger = TokenTriggers.None;
                        break;

                    case FixtureVSLexer.DATE:
                        tokenInfo.Type = TokenType.Literal;
                        tokenInfo.Color = TokenColor.Number;
                        tokenInfo.Trigger = TokenTriggers.None;
                        break;

                    case FixtureVSLexer.AT:
                    case FixtureVSLexer.HASH:
                    case FixtureVSLexer.PERCENT:
                    case FixtureVSLexer.COMMA:
                    case FixtureVSLexer.EQUAL:
                    case FixtureVSLexer.COLON:
                        tokenInfo.Type = TokenType.Delimiter;
                        tokenInfo.Color = TokenColor.Text;
                        tokenInfo.Trigger = TokenTriggers.None;
                        break;
                    
                    case FixtureVSLexer.STRING:
                    case FixtureVSLexer.STRING2:
                        tokenInfo.Type = TokenType.String;
                        tokenInfo.Color = TokenColor.String;
                        tokenInfo.Trigger = TokenTriggers.None;
                        break;

                    case FixtureVSLexer.INTEGER:
                    case FixtureVSLexer.DECIMAL:
                    case FixtureVSLexer.HEX_VALUE:
                        tokenInfo.Type = TokenType.Literal;
                        tokenInfo.Color = TokenColor.Number;
                        tokenInfo.Trigger = TokenTriggers.None;
                        break;

                    case FixtureVSLexer.LBRACE:
                    case FixtureVSLexer.RBRACE:
                    case FixtureVSLexer.LBRACKET:
                    case FixtureVSLexer.RBRACKET:
                        tokenInfo.Type = TokenType.Delimiter;
                        tokenInfo.Color = TokenColor.Text;
                        tokenInfo.Trigger = TokenTriggers.None;
                        break;

                    case FixtureVSLexer.COMMENT:
                        tokenInfo.Type = TokenType.Comment;
                        tokenInfo.Color = TokenColor.Comment;
                        tokenInfo.Trigger = TokenTriggers.None;
                        break;

                    default:
                        tokenInfo.Type = TokenType.Text;
                        tokenInfo.Color = TokenColor.Text;
                        tokenInfo.Trigger = TokenTriggers.None;
                        break;
                }

                _state.InBlockComment = _lexer.InBlockComment;
                _state.InBlockString = _lexer.InBlockString;
            }

            // we reached the end of the line or an unrecognizable token and need to stop parsing this line anyway.
            if (string.IsNullOrEmpty(token_text))
                return false;

            // set the TokenInfo fields to match the location/length of the current token
            tokenInfo.StartIndex = Offset;
            Offset += token_text.Length;
            tokenInfo.EndIndex = Offset - 1;

            state = _state.State;
            return true;
        }
    }
}
